#ifndef BUFFEREDOUTPUTBOXIMPL_H_JM_20121107
#define BUFFEREDOUTPUTBOXIMPL_H_JM_20121107
/** @file BufferedOutputBox.h
 * _OK_ Rozsirenie chovania boxov o bufferovany vystup.
 **/

#include "RootBox.h"

namespace jm4bbx {

	template<typename valueT>
	class BufferedOutputBoxImpl { 
	public:

		/// Vystupny typ.
		typedef valueT ValueType;

		BufferedOutputBoxImpl(RootBox* box, const outType& a, const groupType& g, 
			const columnType& c, const int chunkSize) 
			: box_(box)
			, OUT_ARC(a)
			, OUTPUT_GROUP(g)
			, OUTPUT_COLUMN(c)
			, RESULT_CHUNK_SIZE(chunkSize)
			, messageRecordCounter_(0) {
		}		

	
		/// Je vystup resp. vystupna sprava naplnena?  
		/// Data sa posielaju v spravach, ktore maju pevnu kapacitu 
		/// {@link RESULT_CHUNK_SIZE}. Pokial je sprava naplnena (tj. metoda 
		/// vrati true) tak sa dalsie vkladane data budu ukaldat do bufferu.
		/// Pre najoptimalnejsie fungovanie je vhodne odosielat spravy 
		/// {@link sendOutputData()} az po naplneni spravy. Metoda detekuje 
		/// len naplnenie vystupu ale nekvatifikuje ho, tj. po osolani 
		/// spravy moze byt vystup stale plny pokial pocet dat na vystupe 
		/// presahuje dvojnasobok velkosti spravy.
		inline bool isOutputFull() const {
			return RESULT_CHUNK_SIZE <= messageRecordCounter_ + outputBuffer_.size();
		}

		/// Su na vystupe cakajuce data na odoslanie?
		/// Reflektuje vsetky data buffer + sprava.
		inline bool isOutputEmpty() const {
			return messageRecordCounter_ + outputBuffer_.size() <= 0;
		}
		/// Pridaj vysledok na vystup.
		void addOutputData(const ValueType& result) {
			if (isOutputMessageFull()) {
				// Pokial je sprava plna -> treba data bufferovat.
				outputBuffer_.push_back(result);			
			}
			else {
				addData2OutputMessage(result);
			}
		}
	
		/// Odosli data.
		bool sendOutputData() {
			if (!isOutputMessageFull()) {
				// Presun dat z bufferu pokial je v sprave este volne miesto.
				moveBufferedDataToMessage();
			}
			bool sent = messageRecordCounter_ > 0; // Posielam som vobec nieco?
			if (sent) {
				// Nastavenie rozsahu dat + fyzicke odoslanie
				box_->getGroupPtr(OUTPUT_GROUP)->relocate(0, messageRecordCounter_);
				box_->send(OUT_ARC, OUTPUT_GROUP);
			
				// Znulovanie citaca dat v sprave a alokovanie noveho priestoru pre
				// vystup.
				messageRecordCounter_ = 0;
				allocateResultChunk();
			}
			return sent;
		}


		void initBufferedOutput() {
			allocateResultChunk();
		}

	private:	
		RootBox* box_;
		const outType OUT_ARC;
		/// Vystupna skupina.
		const groupType OUTPUT_GROUP;

		/// Vystupny stlpec.
		const columnType OUTPUT_COLUMN;

		/// Buffer pre vysledky, ktore sa nezmestia do vystupnej spravy pocas 
		/// jedneho spracovania.
		std::vector<ValueType> outputBuffer_;

		/// Citac poctu zaznamov-vysledkov vlozenych na vystup.
		size_t messageRecordCounter_;

		/// Maximany mozny pocet dat poslatelnych jednou vystupnou spravou.
		/// Prilis male cislo -> vela opakovani -> zbytocna rezia.
		/// Prilis velke cislo -> zbytocna alokacia.
		/// Nutne toto musi byt konstanta pretoze alokacia pamate je odkladana
		/// takze volanie allocate() nesmie byt bezprostredne pred pouzitim 
		/// pamate.
		const size_t RESULT_CHUNK_SIZE;

		/// Presun data z buferu do spravy. 
		/// Presunie sa maximalny mozny pocet dat tj. pokial nie je sprava plna. 
		/// Data, ktore sa do spravy nezmestia zostanu v befere.
		void moveBufferedDataToMessage() {
			while (!isOutputMessageFull() && !outputBuffer_.empty()) {
				addData2OutputMessage(outputBuffer_[outputBuffer_.size() - 1]);
				outputBuffer_.pop_back();
			}
		}

		/// Alokovanie noveho priestoru pre vystup.
		void allocateResultChunk() {
			//allocate(OUTPUT_GROUP, RESULT_CHUNK_SIZE);
			box_->getGroupPtr(OUTPUT_GROUP)->allocate(RESULT_CHUNK_SIZE);
		}

		/// Pridaj data do vystupu - natvrdo bez kontroly volneho miesta.
		/// @see isOutputFull()
		inline void addData2OutputMessage(const ValueType& r) {
			assert(!isOutputMessageFull());
			box_->get_data<ValueType>(OUTPUT_COLUMN)[messageRecordCounter_++] = r;
		}

		/// Je vystupna sprava naplnena? 
		/// Testuje len pocet dat vo vystupnej sprave tj. nezohladnuje data 
		/// vo vystupnom buffere.
		/// @see isOutputFull() je urcene pre transparentnu detekciu stavu 
		/// vystupu (nerozlisuje bufferovane a nebufferovane data).
		/// @return true sprava je plna a uz nepojme dalsie data. 
		/// @return false sprava nie je plna a mozno vkladat dalsie data. 
		bool isOutputMessageFull() const {
			return RESULT_CHUNK_SIZE <= messageRecordCounter_;
		}
	};
	
} // namespace jm4bbx

#endif
